Skip to content

feat(spec): the flow end node declares its outcome — refused with an interpolated message, and the run vocabulary gains refused - #15889

Merged
os-project-manager merged 6 commits into
mainfrom
claude/issue-14945-flow-end-node-refused-outcome
Sep 5, 2026
Merged

feat(spec): the flow end node declares its outcome — refused with an interpolated message, and the run vocabulary gains refused#15889
os-project-manager merged 6 commits into
mainfrom
claude/issue-14945-flow-end-node-refused-outcome

Conversation

@claude

@claude claude Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Fixes #14945

Lane (1) of maintainer ruling 2′ (issue comment 5548735593, decision batch #42, 「13753 我让别人处理了,其他同意」): the spec contract. Lane (2) — service-automation honouring refused at the end handling and persisting the outcome / rendered message — is #15788 (Blocked-by: #14945, not addressed here). Lane (3) — the console FlowRunner rendering Close-only — is objectstack-ai/objectui#7707 (not addressed here). This PR edits packages/spec only.

What the contract now says

end node config — EndConfigSchema (packages/spec/src/automation/builtin-node-config.zod.ts, exported from @objectstack/spec/automation), a strictObject:

key shape describe
outcome z.enum(['completed', 'refused']).default('completed') "How the run ends when it reaches this node. completed (the default) is the ordinary terminal. refused is a first-class refusal: the run records refused — distinct from failed, a refusal is a successful evaluation that says no — carries the rendered message, is never resumed, and a runner shows the message with Close only: no Submit, no completion toast."
message z.string().min(1).optional() "Why the run was refused, as a {token} template interpolated at run time exactly like a screen description ({record.name} etc.), so the text names the record. Required when outcome is refused; refused when it is completed — a completion renders nothing, so the key would be a silent no-op."

A superRefine keeps the pair honest in both directions: outcome: 'refused' with no messagecustom issue at ['message'] ("outcome: 'refused' requires a message — a refusal with no text is the shape this contract exists to replace …"); message with outcome: 'completed' or omitted ⇒ custom at ['message'] ("message is only rendered when outcome is 'refused' … silent no-op …"). Aliases reason / text / descriptionmessage (the screen-node spelling a refusal migrates OUT of), status / resultoutcome; title carries guidance ("An end node has no heading"). The history line names the before-state: "Until this shape was declared, an end node had no config contract at all — any key was accepted at parse and ignored at run time, so a refusal an author wrote here shipped as a plain completion." Same TSDoc / describe wording as ScreenConfig.description for the template — no second dialect, no .meta marker (screen's description carries none either).

Where it is applied — FlowNodeSchema (flow.zod.ts). end is structural (FLOW_STRUCTURAL_NODE_TYPES; service-automation/src/engine.ts:7345 returns before any executor), so neither door every other builtin's config passes through exists for it — no descriptor configSchema at registerFlow(), no execute-time parse(). The flow parse is its only door, so the node transform now runs parseEndNodeConfig after parseFlowNodeRegions: a type: 'end' node's config is parsed against EndConfigSchema, issues re-raised under ['config', …] (a flow-level parse reports nodes[i].config.message), and the parsed (defaulted) config written back. A node with no config is left without one. Every other type's config stays the open, executor-owned slot (ADR-0018) — pinned.

Run row (execution.zod.ts): ExecutionStatus gains 'refused' (appended LAST so every .options index reader keeps its positions); ExecutionLogSchema gains refusalMessage?: string — "Rendered end node message when status is refused — the per-record text the flow refused with. Absent on every other status."

Result / wire (contracts/automation-service.ts, api/automation-api.zod.ts): AutomationResult.status and TriggerFlowResponseSchema.data.status gain 'refused'; both gain refusalMessage?: string beside successMessage / errorMessage. On a refusal success is true (the evaluation succeeded), successMessage is absent (nothing to toast). The status pin (contracts/automation-result-status.pin.test.ts) now holds five members on both sides; the compile-time Eq binding of TriggerFlowResponse['data'] to AutomationResult holds.

Before / after — the card-shape probe (FlowSchema.safeParse, { id: 'end', type: 'end', label: 'End', config })

config before (c99449ab5) after
{ outcome: 'refused', message: 'No — {record.name} is a duplicate' } accepted (opaque bag) accepted; config written back verbatim
{ outcome: 'refused' } accepted refused — custom at ['nodes', 1, 'config', 'message']
{ outcome: 'refused', message: '' } accepted refused — too_small at ['nodes', 1, 'config', 'message']
{ outcome: 'completed', message: 'silent' } / { message: 'silent' } accepted refused — custom at ['nodes', 1, 'config', 'message']
{ outcome: 'bogus' } accepted refused — invalid_value at ['nodes', 1, 'config', 'outcome']
{ outcome: 'refused', message: 'x', reason: 'y' } accepted refused — unrecognized_keys at ['nodes', 1, 'config'], "Did you mean reasonmessage?"
{} accepted, {} accepted, { outcome: 'completed' }
ExecutionStatus.safeParse('refused') false true

formatZodError renders the refusal as ✗ nodes.1.config.message: \outcome: 'refused'` requires a `message` …. Measured boundary: a region-nested end(inside aloopbody) is NOT refused by the flow parse alone —parseFlowNodeRegionsdeliberately leaves a region it cannot parse untouched (the registration walk owns nested diagnostics, #4389) — and IS refused byvalidateControlFlow, which re-parses the region through FlowNodeSchemaand throws namingloop 'each' body` and the same sentence. Pinned as measured.

PM mechanism assumptions — verified

  • H1 falsified on the mechanism, right on the landing file. There is no node discriminated union: FlowNodeSchema.config is an open z.record (ADR-0018) and b91c351e5 (fix(spec): send a top-level flow trigger to the START node config, not to a type rename #14735) added flow-level guidance sending trigger to the start node, not a start-node config schema (nothing named StartConfig exists in spec; the start config is read structurally by flow-trigger-kind.ts and the engine). The precedent mirrored is ScreenConfigSchema (a strictObject in the same module); the wiring is the node transform, because end has no executor door. The before-state probe is above.
  • H2 incomplete: two status vocabularies, not one. ExecutionStatus is the run-row vocabulary; the runner reads the RESULT vocabulary (AutomationResult.status mirrored on TriggerFlowResponseSchema.data.status — objectui flowResponse.ts:108 branches on it). Both widened here; both in packages/spec. The run record carries NO run-level error/message key today (a failure's reason is steps[].error; successMessage / errorMessage live on the RESULT, copied from the flow definition), so there was no sibling to sit beside — refusalMessage is the new key on both the run row and the result (shape choice recorded in the report's open_questions).
  • H3 falsified on location. flow.form.ts carries only the flow-level sections (nodes is a repeater); no per-node form lives in spec. The screen node's description row is the descriptor configSchema in service-automation/src/builtin/screen-nodes.ts:64, and objectui builds inspectors from descriptors (inspectors/flow-node-config.ts, json-schema-to-fields.ts) with 'end' in its hand-written FLOW_NODE_TYPE_OPTIONS (:1203). The designer row for end is therefore lane 2's (an end descriptor with configSchema reconciled by builtin-node-form-zod-ledger.test.ts — add { nodeType: 'end', zod: EndConfigSchema } there) or lane 3's hand-written inspector. The JSON-schema row lane 2 copies is z.toJSONSchema(EndConfigSchema)outcome enum with default: 'completed', message string, the describe texts above.
  • H4 falsified: node-config keys are not ledgered. liveness/flow.json carries nodes.children.config as ONE live row (the open record); no screen.description / waitForInput row exists. EndConfigSchema is not a registered metadata type, so the walker never reaches outcome / message — no rows to add, check:liveness green unchanged. The honest not-yet-honoured status is stated where an author reads it: the EndConfigSchema TSDoc, the docs callout ("Declared first — the engine and runner halves follow"), and the changeset.
  • H5 stated in the outcome describe, the AutomationResult.status JSDoc and the docs; nothing about resumption pinned here.
  • sys_automation_run is not a spec object (no .object.ts; it is the durable store service-automation/src/index.ts:104 registers) — lane 2's.

Reader census

ExecutionStatus / result-status reader census (origin/main c99449ab5, re-located by symbol; the list #15788 consumes)

Readers that widen automatically (read .options or the enum itself — nothing to change):

  • packages/spec/src/api/automation-api.zod.ts:422ListRunsRequestSchema.status: ExecutionStatus.optional()
  • packages/spec/src/automation/execution.zod.ts:464ScheduleStateSchema.lastRunStatus
  • packages/runtime/src/domains/automation.ts:1822parseEnumParam('status', …, ExecutionStatus.options); the comments at :1799 / :1810 say "8 members" — prose only
  • packages/runtime/src/query-param.ts:113,180 — comments naming "the eight ExecutionStatus members" — prose only
  • packages/client/src/index.ts:157,4256,6560 — type-only re-export / listRuns option type
  • packages/spec/src/contracts/automation-service.ts:555listRuns option type
  • packages/services/service-automation/src/engine.ts:6,3660 — import + listRuns option type

Readers that must LEARN refused (lane 2 = #15788, or its consumers):

  • packages/services/service-automation/src/engine.ts:7345if (node.type === 'end') return; — the end handling that has to read EndConfigSchema, interpolate message, stamp status: 'refused' + refusalMessage on the run row and the result (success: true, no successMessage); terminal-status writes today at :4430, :4533, :5522, :5781, :8441 (status: 'failed'), the completed counterparts beside them
  • packages/services/service-automation/src/engine.ts:1333,5561,6293 — comments asserting "ExecutionStatus … is unchanged / carries no such member" (stranded ruling prose) — still true for stranded; re-read when stamping refused
  • packages/plugins/plugin-approvals/src/approval-service.ts:262-271classifyStrandedRunState documents "the engine writes exactly four run statuses (paused, completed, failed, cancelled)"; its default arm stays SILENT on an unknown status, so a refused run is not misreported as stranded — but the release-on-terminal path pinned by approval-service.test.ts:2577 (it.each(['completed', 'cancelled', 'timed_out'])) does not list refused: an approval request whose run ends refused must be released too (decide in lane 2 / approvals)
  • packages/plugins/plugin-approvals/src/stranded-request-inspection.test.ts:340-347 — pins that an unrecognised status is NOT a strand (uses timed_out); refused inherits that behaviour
  • packages/runtime/src/domains/automation-runs-query-validation.test.ts:320-322it.each([8 members])('forwards every declared ExecutionStatus member') — a hand-copied list; stays green with 9 members but no longer covers refused (extend in lane 2 or with the runtime half)
  • packages/rest/src/rest-server.ts:11983 — reads status === 'stranded' for the repairable envelope (result vocabulary); refused needs no arm there (success: true, 200)
  • objectui packages/app-shell/src/utils/flowResponse.ts:108,228-245status?: 'completed' | 'paused' | 'failed' | string; today a refused envelope falls through to kind: 'done' (the completed toast) — lane 3 (objectui#7707) adds kind: 'refused' reading refusalMessage; FlowRunner.tsx:186-189 is the toast site, :295,304-316 the terminal Close-only footer the refusal reuses

Mirrors of the RESULT status vocabulary (all widened here, in spec): AutomationResult.status (contracts/automation-service.ts:350), TriggerFlowResponseSchema.data.status (api/automation-api.zod.ts:343), the pin contracts/automation-result-status.pin.test.ts (five members), the compile-time Eq binding in api/automation-api.zod.test.ts:52 (holds with refusalMessage on both sides).

Docs

content/docs/automation/flows.mdx: the end row in the Node Types table; a new ### Ending a run — \completed` or `refused` section (#end-node-outcome) with the example, the semantics in the ruling's words, the interpolation note, the Close-only runner behaviour stated as the contract, and a callout naming #15788 / objectui#7707 as where the halves land; the Observing-runs paragraph now lists refusedamong the mirrored terminal statuses.approvals.mdx/workflows.mdx measured: neither enumerates terminal run statuses in a way this change falsifies (approvals' "terminal without a decision" list is about the stranded sweep; the approvals reader question is in the census) — untouched. Generated references (builtin-node-config.mdx, execution.mdx, api/automation-api.mdx, index.mdx) regenerated by gen:docs. content/docs/permissions/system-context.mdx:196re-anchored bycheck-system-context-census --fix(pure line rot from the lines added aboveflow.zod.ts:702`).

Changeset

.changeset/flow-end-node-refused-outcome.md@objectstack/spec minor. Additive: nothing renamed or retired, ADR-0087 disposition not-required, no migrations/registry.ts edit.

Pins (spec)

packages/spec/src/automation/end-node-outcome.test.ts (new, 29 cases) — EndConfigSchema default / card shape / refused-without-message (code + path + message substance) / empty message / message-on-completed (explicit and omitted outcome) / bogus outcome / strict unknown key with suggestion / description and status aliases / title guidance; FlowSchema card-shape probe with write-back / default write-back / no-config left alone / the same refusals at ['nodes', i, 'config', …] / formatZodError rendering / plugin-node config stays open / defineFlow round-trip and refusal / region-nested end at the validateControlFlow door; run row: refused beside failed, refusalMessage preserved and optional. execution.test.ts: refused in the valid set, appended last, rejected still refused. automation-result-status.pin.test.ts: five members, a refused envelope PRESERVED on the wire (success: true, refusalMessage, no successMessage), the JSDoc names the condition. Existing flow.test.ts pins (including #15716's edges[] block) green.

Ablation

Script: a trap restore EXIT INT TERM shell with absolute paths (REPO_ROOT from git rev-parse --show-toplevel), run under the verify lock on HEAD 76106f88e. Resolution path asserted first: end-node-outcome.test.ts:22 imports ./builtin-node-config.zod relatively from src, and packages/spec/vitest.config.ts declares no alias (0 matches) — the suite reads source, so no build leg applies. Mutation: the EndConfigSchema superRefine body replaced by a no-op carrying an ABLATION_14945_NOOP marker; landing proven by anchor / marker counts (if (config.outcome === 'refused') { 1 → 0, marker 0 → 1), worktree blob 54fc9d4c…. Run over end-node-outcome.test.ts + flow.test.ts + builtin-node-config.test.ts: 9 failed / 163 passed — exactly the nine pins that assert the refused⇔message pairing (three on EndConfigSchema directly, four on FlowSchema incl. the formatZodError line, the defineFlow refusal, the region-door case); every unknown-key, enum, default, write-back and existing pin stayed green (direction: red, as expected). Restore: git checkout HEAD -- ABS_PATH, proven by git hash-object == git rev-parse HEAD:PATH (c8f4539e9ca6f647200ab350211bd564fb0f9ba2 both) and git diff HEAD empty; git status --porcelain 0 lines after.

Verification

Base c99449ab5 (worktree from origin/main 08:02Z); origin/main merged at 8e7653b35 via scripts/pm/os-regen-merge.sh (merge 7126641be, regeneration commit 76106f88e — the three os-regen paths staged from main's side were regenerated on the merged tree and the STAGED content checked by quoted-exact-name before committing; the six spec files sibling PRs #15804 / #15798 / #15792 / #15786 / #15782 / #15707 touched are byte-identical between this HEAD and origin/main). Every build/test below ran through scripts/pm/os-verify-lock.sh and is quoted from its VERDICT command-exit line; exit codes captured before any pipe.

@objectstack/spec (full)pnpm --filter @objectstack/spec build green; check:generated: "All 15 generated artifacts are up to date" (after --fix regenerated the five it proved stale: api-surface, export-origins, declaration-map, docs, strictness-ledger); vitest run on HEAD 76106f88e: 475 files / 12,782 tests passed (VERDICT command-exit 0, held 461s); typecheck (tsc + check:scripts-typecheck + check:test-typecheck) green via turbo.

Consumer readings (downstream direction, ...@objectstack/spec; closures built cold first — core^..., service-automation^..., client^..., lint^..., runtime^..., rest^..., cli^...; turbo ls --affected from $BASE lists 75 packages, narrowed to the seven the dispatch names — declared narrowing, the rest is CI's):

  • turbo run typecheck for spec + service-automation + runtime + rest + client + lint + cli + core: 64 tasks successful (VERDICT command-exit 0) at b9ea7d41f.
  • service-automation vitest run: 109 files / 1,303 tests passed at b9ea7d41f.
  • client 33 / 437, core 49 / 1,190, lint 97 / 3,327 passed at b9ea7d41f.
  • runtime vitest run: 226 / 3,243 at b9ea7d41f; re-run on the merged head 76106f88e: 227 / 3,256 passed.
  • rest vitest run: 180 / 3,086 passed at b9ea7d41f.
  • cli vitest run --project unit: 175 / 176 files at first pass — the one red, test/published-subpath-hook-body.pin.test.ts, was PREREQUISITE NOT MET (packages/cli is not built); after turbo run build --filter=@objectstack/cli it passed (1 file / 13 tests) — so 176 files / 2,371 tests green; the integration layer is declared to CI (the diff touches no spawn entry).
    The named-consumer suites were measured at b9ea7d41f; the merge that followed touched none of the spec files this PR edits (only sibling changesets and unrelated packages), and the ratchet/gate families were re-run on the final head (below).

Gate readingsnode scripts/pm/dispatch-gates.mjs (no paths) derived 94 owed families (82 path-matched + 5 kind + 7 whole-tree) at fe4ba76f4; harvested with --commands. Run on the final head 76106f88e with per-command exit codes: 94 commands run, 87 exit 0 on the first pass, 7 exit 1 — all 7 the same dist/**/*.d.ts is OLDER than packages/spec/src freshness guard (check:api-surface, check:browser-reachable-entries, check:dual-source-exports, check:entry-nameability, check:exported-any, check:generated, check:skill-examples), tripped by the ablation's byte-identical restore bumping the source mtime after the last build (dist content was current: EndConfigSchema present in automation/index.d.ts, no ablation marker); rebuilt @objectstack/spec under the lock (no tree change, porcelain 0) and re-ran those seven: all exit 0. 2 families deferred as build-heavy / whole-workspace prerequisites (check:dual-build-cjs-loads, check:type-check-debt; the turbo/vitest entries are the suites reported above). Two real findings during the run, both fixed in-branch: check:doc-anchors (a bracket anchor on a bold paragraph renders no heading id — the section is now a real ### heading) and check-system-context-census (pure line rot from the lines added above flow.zod.ts:702--fix re-anchored system-context.mdx). Additional spec audits run individually and green: check:variant-docs (not a discriminated union; 18 unions, 8 governed), check:liveness, check:strictness-ledger, check:exported-any, check:dual-source-exports, check:entry-nameability, check:browser-reachable-entries, check:empty-state, check:llms-txt, check:yaml-examples, check:error-code-provenance, check:meta-url-spelling, check:spec-changes, check:react-blocks, check:skill-examples (after building client-react: 257 prose examples type-check), check:cross-package-test-inputs, check:type-check-coverage. NOT MEASURED locally (prerequisite: eleven packages unbuilt — a whole-workspace build): check:dual-build-cjs-loads, check:type-check-debt --re-measure — CI's. pnpm lint (repo-wide eslint) not run locally — CI's.

Open questions (for contract review)

  1. Where the rendered message lives — chosen: a flat refusalMessage?: string on the run row (ExecutionLogSchema) and on the result (AutomationResult / TriggerFlowResponse.data), mirroring the flat successMessage / errorMessage idiom. Alternative: refusal: { nodeId, message } (names WHICH end refused when a flow has several). Four-axis reasoning in the report; the reviewer can veto cheaply before service-automation: honour outcome: 'refused' on the flow end node — a terminal refused run status (distinct from failed) with the interpolated message persisted on the run (lane 2 of the #14945 ruling 2′) #15788 starts.
  2. success on a refused result — chosen true (the ruling: "a successful evaluation that says no"); a runner keys on status, never on success, for the Close-only rendering.

Generated by Claude Code

os-project-manager and others added 4 commits September 5, 2026 08:39
…interpolated message (wip)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M59rPZZFzqhfMUPFqqZTkf
…edgers for the end-node outcome (wip)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M59rPZZFzqhfMUPFqqZTkf
…rging origin/main

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M59rPZZFzqhfMUPFqqZTkf
@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tests tooling labels Sep 5, 2026
@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 11 documentable anchor(s). ⚠️ 6 changed file(s) yielded no anchor (packages/spec/api-surface/automation.json, packages/spec/authorable-defaults/automation.json, packages/spec/authorable-surface/automation.json, …), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/api/client-sdk.mdx (via AutomationResult (symbol, a top-level interface))
  • content/docs/automation/flows.mdx (via AutomationResult (symbol, a top-level interface), refusalMessage (symbol, a field of interface AutomationResult))

1 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17.mdx (via AutomationResult (symbol, a top-level interface), FlowNodeSchema (symbol, a top-level const))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 6 changed file(s) yielded no anchor (packages/spec/api-surface/automation.json, packages/spec/authorable-defaults/automation.json, packages/spec/authorable-surface/automation.json, …) — pages documenting those are invisible to this run
  • the SDK route bridge reached 61 of 219 client-bound route-ledger rows — the other 158 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 158: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 129 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json ea03c7c16fc27dcd489378faf8ba99abf9ae7124packageMentionDocs.

Which tree this was computed on

This run read content/docs from 368dff7b770155eef87119119ad13c1eb3695de5 — the merge of head 87f6afcd20679030e4dbc55f2be52815b1482898 into base ea03c7c16fc27dcd489378faf8ba99abf9ae7124, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 368dff7b770155eef87119119ad13c1eb3695de5 && git checkout 368dff7b770155eef87119119ad13c1eb3695de5
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin ea03c7c16fc27dcd489378faf8ba99abf9ae7124 87f6afcd20679030e4dbc55f2be52815b1482898 && git checkout -B drift-repro ea03c7c16fc27dcd489378faf8ba99abf9ae7124 && git merge --no-ff 87f6afcd20679030e4dbc55f2be52815b1482898

node scripts/docs-audit/affected-docs.mjs --json ea03c7c16fc27dcd489378faf8ba99abf9ae7124

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs ea03c7c16fc27dcd489378faf8ba99abf9ae7124 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

…nchor after merging origin/main (lap 2)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M59rPZZFzqhfMUPFqqZTkf
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

2 participants